/*
Online Java - IDE, Code Editor, Compiler

Online Java is a quick and easy tool that helps you to build, compile, test your programs online.
*/

// This has been created to ensure I can utilize any random functions more efficiently.
// It is a creation of the NcR    combinations calculator.
// It has used techniques I learnt including recursion and also memoization to speed up execution.
// I will incorporate this into Java applications I created previously..

//ISSUES:
// THIS HAS BEEN CREATED USING n = 3 and r =3
// IT HAS NO ISSUES WITH THESE SET.
// THE FIRST PART WILL BE TO TRY AND ACCOMODATE FOR A LARGER   n=4  and r=4
// HENCE I HAVE INCLUDED ONE COMMENTED SWITCH STATEMENT ALSO
// I CAN THEN TRY TO SEE WHAT HAPPENS IF R is reduced
// UNLIKE OTHER PROBLEMS LIKE GENERATING AND CHAINING A RANDOM NUMBER INTO BIGGER ONE, 
// THERE IS A BIT MORE TECHNICAL PROCESSING TO TRUNCATE THE SET ENTRY AND PERFORM CONVERSIONS FOR CURRENCIES...
// IT IS ALSO OPEN TO INTRODUCING TRANSACTION FEES IN THE SWITCH STATEMENT
// HOWEVER THIS IS NOT PART OF THE PROBLEM

// I HAVE NOW ADJUSTED SO THAT IT DOES NOT SHOW CONVERSIONS IF THE SET HAS ALREADY BEEN PROCESSED.

// NEED TO REMEMBER   SAMPLE IS R     AND OBJECTS IS N....
// R IS SMALLER THAN N

// Tried following... its largely successful... not sure how to get any closer....
//r=2  n=3     PASS
//r=4  n=4     PASS
//r=2  n=4     PASS
//r=3  n=4     PASS
//r=2  n=2     FAILS (it processes n currencies)
//r=3  n=3     FAILS (it processes n currencies)
//r=1  n=2     PASS  (THE CODE BREAKS SINCE ONLY ONE CURRENCY, NEED TO PROMPT END USER)
//r=1  n=1     FAILS  (it processes n currencies)
//r=4  n=4    FAIL


//r=2   n=5     PASS
//r=3   n=5     PASS
//r=4   n=5     PASS
//r=5   n=5     PASS

// ALL FIXED....!!!


import java.math.*;
import java.util.*;
import java.math.RoundingMode;    // used for round to 2dp
import java.text.DecimalFormat;   // used for formatting number 

class conversions
{
    public conversions ()
    {
        
    }
}


class exchangeCombinations
{
    DecimalFormat format = new DecimalFormat("##0. 00");  //format to 2dp..
    
    String currencyExchange="";   // this is used to hold chain of exchanges
    double exchangeRate;          // this is exchange rate    
    double amount=50.0;          //amount to start transactions
    double currency;   // hold values of amount * exchangeRate;
    //String temp1;  // this holds the value of numberArray, which is the symbol
    int k;   // this will hold offset value of 4 for delimiter...  £=>$   in context of substring to represent this
    String fullConversionsReverse; // this will hold ALL forward currency exchanges in reverse
    String exchangeArbitrageFlipped;  // it holds conversion reverse direction as such £=>$
    String outcome="";    // this will hold the exchange undertaken  in format £X.XX
    Double ArbitrageChecker;  // this holds difference between initial amount and final amount (reverse conversion)
    String fullConversions;  // opposite fullConversionsReverse.  ALL Transactions in forward direction..
    String exchangeArbitrage;  // it holds the conversion such as £=>$. It is based on fullConversions
    String newCurrency;  // this is the currency that it will be exchanged into.
    String initialAmount;  // This can be changed at any point of conversions. Useful if starting currency is too small
    long combinations;  // all combinations from NcR calculator.
    int count;   //number cycles undertaken. Used to give indication of executions to store combinations in Set...
    String temp;  // used to hold currency symbol
    StringJoiner sj = new StringJoiner("=>");   // all currencies will be added and get => as a seperator
    Set <String> s = new HashSet <>();   // this will store the  combinations
    
    int startPos;  // used in arbitrageCheckerTwoCurrencies and arbitrageCheckerTwoCurrencies to traverse fullConversions
    int endPos;  // used in arbitrageCheckerTwoCurrencies and arbitrageCheckerTwoCurrencies to traverse fullConversions
    
    List <String> lst = new ArrayList<>(Arrays.asList("$","£","€","¥","F"));  // this is list of currencies. In future, it will be extended.
    List <String> copy = new ArrayList<>(lst);  //keeps a copy    // this list keeps a copy since during program execution the top list is modified
    
    StringBuilder sb;  // used for fullConversions string... And then to use the reverse method for fullConversionsReverse
    int currentSetSize;  // used to check if Set incremented, otherwise no point demonstrating screen output again
    boolean exitCondition;  // used in do while loop... the exit point varies depending on scenario
    int n;  // total number of Objects selected
    int r;  // total sample size selected
    int differenceObjectsAndTotalCurrenciesAvailable;  // used if n chosen differs to  n objects available.
    int randomNumber;    // random  number generated.. This will be zero index based.
    String currencySymbol="";     // value at index of randomNumber in the set
    String conversion;  //this is chain of conversion
    int totalCurrenciesAvailable; // this is same value as size of list
    
    Random rand = new Random();  // random seed used to generate random number
    
    public String currencyExchange(String exchangeCurrencies, String currencySymbol)
    {
        // this holds the exchange rates and calculate new currency. 
        switch (exchangeCurrencies)
    {
        case "F=>Â¥":
            exchangeRate=174.59;
            currency = amount*exchangeRate;
            break;
            
        case "F=>£":
            exchangeRate=0.89;
            currency = amount*exchangeRate;
            break;
            
        case "F=>$":
            exchangeRate=1.16;
            currency = amount*exchangeRate;
            break;
            
        case "F=>€":
            exchangeRate=1.07;
            currency = amount*exchangeRate;
            break;
        
        case "¥=>£":
            exchangeRate=0.0051;
            currency = amount*exchangeRate;
            break;
        
        case "Â¥=>$":
            exchangeRate=0.0067;
            currency = amount*exchangeRate;
            break;
            
        case "¥=>€":
            exchangeRate=0.0061;
            currency = amount*exchangeRate;
            break;
            
         case "Â¥=>F":
            exchangeRate=0.0057;
            currency = amount*exchangeRate;
            break;
        
        case "$=>£":
            exchangeRate=0.83;
            currency = amount*exchangeRate;
            break;
            
        case "$=>€":
            exchangeRate=0.92;
            currency = amount*exchangeRate;
            break;
        case "$=>Â¥":
            exchangeRate=149.97;
            currency = amount*exchangeRate;
            break;
            
         case "$=>F":
            exchangeRate=0.87;
            currency = amount*exchangeRate;
            break;
            
        case "£=>$":
            exchangeRate=1.30;
            currency = amount*exchangeRate;
            break;
        case "£=>€":
            exchangeRate=1.20;
            currency = amount*exchangeRate;
            break;
            
         case "£=>¥":
            exchangeRate=195.30;
            currency = amount*exchangeRate;
            break;
        
        case "£=>F":
            exchangeRate=1.12;
            currency = amount*exchangeRate;
            break;
            
        case "€=>$":
            exchangeRate=1.09;
            currency = amount*exchangeRate;
            break;
        case "€=>£":
            exchangeRate=0.83;
            currency = amount*exchangeRate;
            break;
         case "€=>¥":
            exchangeRate=162.65;
            currency = amount*exchangeRate;
            break;
            
        case "€=>F":
            exchangeRate=0.93;
            currency = amount*exchangeRate;
            break;
        
    }
    
    amount=currency;   // new amount is value in currency...

    return currencySymbol+format.format(currency);  // new currency
    
     }
    
    //basic validation if incorrect value(s) for sample(r) and objects(n)
    public String checkSelection(int n, int r)
    {
        if (r==1 || n==1)
        {
            return "Sample size (r) or Objects (n) is too small";
        }
        
        if (r>n)
        {
            return "Sample size too big";
        }
        
        if (r<n && r==1)
        {
            return "Sample size too small";
        }

	if (r >totalCurrenciesAvailable || n> totalCurrenciesAvailable)
        {
            return "Sample size or objects selected is larger than total currencies available";
        }

        return "";
    }
    
    
    //constructor - self explanatory
    public exchangeCombinations(long combinations, int n, int r)
    {
        this.combinations=combinations;
        
        this.n=n;
        this.r=r;
        
        totalCurrenciesAvailable=lst.size();  // original size of list, currencies available.
        
        System.out.println("Combinations: " + combinations +"\n");   //number combinations without replacement
        System.out.println("Sample size (r): " + r);
        System.out.println("Objects: (n): " + n);
        
        //Exits if error in input
        if (checkSelection(n, r)!="")
        {
            System.out.println(checkSelection(n, r));
            System.exit(0);
        }
        
        //loop will progress until set consists of all  nCr  combinations...
        do
        {
            System.out.println("\n");
            
            lst = new ArrayList<>(copy);     // each time it starts again, it has to get the original ArrayList back
            
            temp="";   // this is used concatenation of value before it is stored in the set....
            
            sj=new StringJoiner("=>");  // used for concatenating strings
            
            do
            {
            // this is crucial part since if the do while loop evaluates as !lst.empty(),  this will be suitable if 
            // the sample(r) is the same value as n (objects).  Since list consists of n objects.
            // But if the end user decides to choose combinations of 2 (r) objects out of 3 (n) sample, the while loop
            // needs adjustment.
            // alternative boolean statements are required.
            
            // currently statement in end of do while is:  
            //  !lst.isEmpty()  -    this is valid for full list
            //  lst.size() >=(n-r)   is required if r is less than n
            
            exitCondition = !lst.isEmpty();   
            // this is default evaluation, irrespective of r and n. It will get overriden accordingly
            
            //initially exitCondition is true (list.isEmpty() = false...   NOT (false) evaluates as true
            // then   exitCondition is false (list.isEmpty()=true.......  NOT (true) evaluates as false  
            
            
            // FOR SOME REASON, NOT ENTIRELY SURE I HAD TO INCLUDE THIS TO BREAK OUT OF THE DO WHILE LOOP
            //HOWEVER IT FIXED ISSUE WHICH WAS MAIN OUTCOME.....
            //IT CAN BE SEEN THAT EXITCONDITION ALWAYS EVALATED AS TRUE (SHOWING LIST NOT EMPTY`)
            // This is detrimental if the list is empty since there can be no selection
            // SO I have just broken out of the do while loop with exit condition above.....
            // This was not needed when n and r were both the same....
            
            if (lst.size()==0)
            {
                break;
            }
                
                randomNumber = rand.nextInt(lst.size());                  //random number between  0 - (size list-1)
                
                temp=currencySymbol;
                // this is needed so that the correct  currency1=>currency2 symbol  can be 
                // examined in switch statement.
                
                currencySymbol = lst.get(randomNumber);   //gets number from the list
                
                conversion = temp + "=>" + currencySymbol;  // this is the chain of conversion
            
                if (temp!="")  // if currencies are not the same, it will start exchange rates
                // this is just clarifying there are two currencies minimum to be processed
                
                {
                    //this is done for a visual check to ensure there are no errors in inputs....
                    //this can be disabled if required....
                    randomTwoCurrencyChecker();
                    
                }
                
                // This is now creating  Symbol => symbol => Symbol  (up to r sample)
                    sj.add(currencySymbol);
              
                
                lst.remove(randomNumber);     // this is important step.. it removes value at this index from list... enforce combination without replacment
                //System.out.println("WHAT IS LIST SIZE RIGHT NOWWWWW: " + lst.size());
            
                //******************************************
                
                if (n>r)   // if end user has selected a smaller sample from n objects    
            {
                // if n is taken to be smaller also than the currencies available in (lst)
                // then the lst size also needs to be shrunk to accomodate this...
                if (n<totalCurrenciesAvailable)
                {
                    // there are this many less objects (n) than total currencies available
                    differenceObjectsAndTotalCurrenciesAvailable = totalCurrenciesAvailable-n;
             
            
            // this would be true at initial execution
            // since lst.size() is greater than n objects by differenceObjectsAndTotalCurrenciesAvailable
            //comparison has to be done on this size
            
            // it checks if this smaller list (regarded to be the new n)
            // is greater than list size after number times(r) a currency has been removed from the list (n) 
            // since do while loop validate condition on exit...
            // it has to break out to ensure that the currency chain consists of r symbols only.....
            
            if ((lst.size()-differenceObjectsAndTotalCurrenciesAvailable>(n-r)))   
            {
                
                //do while loop will continue
                exitCondition =  true;
            }
            else
            {
                //System.out.println("Ending first do while loop");
                
                break;
            }
            }
            
            // this now relates to scenario if n is greater than r
            //n is now equal to totalCurrenciesAvailable (entire list). 
            // This is much easier to follow now...
            // Since the entire list size is considered to be n...
            // Once r sample elements removed, it has to break out.... to ensure
            // r elements are in the conversion chain
            
            if (lst.size()>n-r)   // this would be true at initial execution
            {
                
                
                exitCondition =  true;
                //break;
                 
            }
            else
            {
                break;
            }
            
            }
           
            //We know that n is less than totalCurrenciesAvailable
            //But it will not enter above conditions, since n is also equal to r
            //This gets slightly trickier again, but is required...
            //otherwise scenarios such as r=2  n=2   with higher totalCurrenciesAvailable
            // will process      totalCurrenciesAvailable  x conversions....
            
            if (n==r)
            {
                differenceObjectsAndTotalCurrenciesAvailable = totalCurrenciesAvailable-n;
                
            //now the break will occur if occur if n objects have been processed from the list.
            // This is as oppose to n-r (since both are identical).
            // There should be   differenceObjectsAndTotalCurrenciesAvailable  elements remaining in the
            // list.. once it has reached this, it needs to break out...
            
            if (lst.size()>differenceObjectsAndTotalCurrenciesAvailable)
            {
                // No need to change the exit point
                //instead best to break out since its processed r elements from n objects
                exitCondition =  true;
                //break;
                 
            }
            else
            {
                break;
            }
            }
            } while (exitCondition);    //it will keep processing this
            
            currentSetSize= s.size();
            System.out.println("\n\n************This is current set size: " + currentSetSize+"***********************************");
            
            s.add(sj.toString());    // adding the value to the set.
            
            //it performs this check since if the set has not grown, there is no point
            // in doing arbitration check / screen outputs, since it already in the set
            System.out.println("************This is new set size: " + s.size()+"***********************************");
            
            //System.out.println("set size: " + s.size());   //once this has incremented, the set has grown...
            
            System.out.println("This is saved in the set: " + sj.toString() +"\n");
            
            // Arbitrage check here
            
            fullConversions = sj.toString();
            
            System.out.println("******Arbitrage full check - full combination of currencies:   " + fullConversions);
            
            //so a condition is created as below to ensure it does not perform method call again:
            if (s.size()==currentSetSize)
            {
                System.out.println("*********NOT REQUIRED******************************");
            }
            else
            {
                // this method checks two currencies... It is not really a requirement, but it was used initially
                // before method before developed....
                // it is goood to keep as visual check to ensure no major mistakes in input
                // it also keeps lots of implementation code in this method....
                // in order to derive the full conversion chain.....
                arbitrageCheckerFullCombinationForward();
                
                //this provides detailed breakdown of amounts and arbitration check......
                arbitrageCheckerFullCombinationReverse();
            }
        
        count++;   // this is indication of a cycle.. It is aiming to be close to combinations...
        
        } while (s.size()<combinations);    // this can also be less than or equal to. It does not matter with 
                                             //do while loop
        
        System.out.println("\nNumber cycles: " + count);  // based on random seed and filling of set. It can be
        //any value greater or equal to the combinations.......
        
        System.out.println("Original list: " + copy.toString());  //original list outputted to the screen
        
    }
    
    
    public void randomTwoCurrencyChecker()
    {
        System.out.println("***RANDOM TWO CURRENCY CHECKER************");
                    
                    System.out.println("Current amount: " + temp + format.format(amount));
                    
                    // the chain and converted to are passed into the method
                    System.out.println("This is your new currency: " + conversion + "   =>  " +  currencyExchange(conversion,currencySymbol));
                    
                    // the string in reverse... Note it can not be done with reverse function
                    // since the   " =>" will become  ">="
                    String reverseConversion = conversion.charAt(3) + "=>" + conversion.charAt(0);
                    
                    //System.out.println("This is reverse76: " + reverseConversion);
                    //System.out.println("temp: " + temp);
                    
                    //Same as above but processes currencies in reverse order
                    System.out.println("This is conversion back to original to check arbitrage: " + reverseConversion + "   =>" + currencyExchange(reverseConversion,temp));
                System.out.println("***********************");
                
    }
    
    
    public void arbitrageCheckerFullCombinationForward()
    {
        System.out.println("\n*****FULL FORWARD CONVERSION****");
        
            //System.out.println("Number times conversion to check is: " + (copy.size()+1));
            
            k=4;           // this is set to 4  since $=£ in substring will require exclusion....
            startPos=0;
            endPos = startPos+4;
            
            exchangeArbitrage = fullConversions.substring(0,endPos);  // as described...
            
            amount=50;   // this will be the starting point. It makes no difference of the amount.
            // unless there is consideration for transaction fees. The amount has to be consistent
            //when doing reverse check....
            
            initialAmount =  exchangeArbitrage.charAt(0) + format.format(amount); //gets symbol and amount in 2dp
            
            System.out.println("Current amount (AMOUNT SHOULD RETURN HERE IF NO ARBITRAGE): " + initialAmount); 
             
            //it performs operation for size of sample (r) 
            for (int i=0; i<r; i++)
            {  
                
            newCurrency = Character.toString(exchangeArbitrage.charAt(3));  //zero index.. new char will be fourth character  $=>£
               
            //System.out.println("segment: " + exchangeArbitrage);    
                
                //this calls the switch statement in method currencyExchange
                System.out.println("This is your new currency: " + exchangeArbitrage + "   =>  " +  currencyExchange(exchangeArbitrage, newCurrency));
            
            startPos=endPos-1;   // startpos is not previously endposition due to substring 
            endPos=startPos+4;  // due substring, 4 is set
            
            if (endPos>fullConversions.toString().length())      // if no more currencies in chain 
            {
                break;
                
            }
                  
            exchangeArbitrage = fullConversions.substring(startPos, endPos);  //create substring with offsets above
            
        
    }
        System.out.println("**************");
    }
    
    

    public void arbitrageCheckerFullCombinationReverse()
    {
        System.out.println("\n*****FULL REVERSE CONVERSION****");
        // This now has to go back to original state
            // issue is creating the substring in reverse and directions of => will be flipped
            //easiest starting point is just reversing the string with StringBuilder
            
            sb = new StringBuilder(fullConversions);   
            fullConversionsReverse = sb.reverse().toString();
            System.out.println("Reverse conversion****:                                        " + fullConversionsReverse);   // this is correct
            
            // NOTE THIS NOW DEALS WITH charAt and not substring.. hence exactly zero index based
            startPos=0;
            endPos = startPos+3;
            
            //it is using reverse method  so => is  >=.   Fixed below
            exchangeArbitrage = fullConversionsReverse.charAt(startPos) + "=>" + fullConversionsReverse.charAt(endPos)    ;  // this will take 4 chars such as  £=>$  
            
            //same reasoning as going forward... r is sample size.
            for (int i=0; i<r; i++) 
            {  
                
            newCurrency = Character.toString(exchangeArbitrage.charAt(3));  // this is symbol only
            exchangeArbitrageFlipped = exchangeArbitrage.charAt(0) + "=>" +  newCurrency; //existing currency => new currency
            
            outcome =  currencyExchange(exchangeArbitrageFlipped, newCurrency);    
            // this stores value of new conversion string, calling switch method currencyExchange
            
            //outputs string
            System.out.println("This is your new currency in reverse: " + exchangeArbitrage + "   =>  " + outcome );
            
        
            startPos=endPos;  // since charAt, refers to exact position zero index
            endPos = startPos+3;
            
            if (endPos>fullConversionsReverse.length())  // this is to ensure no outbound exception.....
            {
                break;
            }
            
            //the next conversion of currencies is being prepared in a string
            exchangeArbitrage = fullConversionsReverse.charAt(startPos) + "=>" + fullConversionsReverse.charAt(endPos);

            }
            
            System.out.println("*********");
            
            //method which checks for arbitration
            checkForwardWithReverseConversion();
            
    }
    
    public void checkForwardWithReverseConversion()
    {
        System.out.println("\n**** FINAL CHECK - ARBITRAGE *******");
            
            //difference between outcome and initial amount (both will be same currency)
            ArbitrageChecker = Double.parseDouble(outcome.substring(1)) - Double.parseDouble(initialAmount.substring(1));
            
            //if difference it reports difference and also calculation...
            if (ArbitrageChecker >0)
            {
                System.out.println("- - - Arbitrage: - - - " + outcome.charAt(0) + format.format(ArbitrageChecker) + "  (" + outcome + " - " + initialAmount+")");
            }
            
            //simple statement if no difference...
            else
            {
                System.out.println("- - - NO Arbitrage: - - - ");
            }
            
            System.out.println("***********************************************************************");
    }  // end of method
    
    }

public class combination
{
    public static void main(String[] args) {
        System.out.println("Welcome to Online IDE!! Happy Coding :)");
        
        int originalNumber=5;
        int n=originalNumber;
        int r =3;
        Map <Integer, Long> m = new HashMap<>();
        System.out.println("***COMBINATIONS***  (WITHOUT REPLACEMENT)");
        System.out.println("C(n,r) = n! / (r!(n−r)!)");
        System.out.println("C(" + n+","+r+") = " + n+"!" + " / " + "("+r+"!"+"("+n+"-"+r+")!)");
        
        exchangeCombinations ec = new exchangeCombinations(Combinations (n,r,originalNumber, m), n,r);
        
    }
    
    public static long Combinations (int n,  int r, int originalNumber, Map factorialResults)
    {
        
        // n are objects
        // r is sample
       
       /*
       ***CALCULATION***
        P(n,r) =   n! /  (r!(n−r)!)
        
        */
        
        long result=0;
        int denominator1;
        int denominator2;
        
        if (n>=1)
        {
            // EXAMPLE
            
            // P (5,6)   =   6* 5* 4 * 3 * 2 * 1   /   6! (6-5)!     =    720 / (5! * 1!) =  120 / 5*4*3*2*1  * 1  = 720 / 120 = 6
        
        result = (n* (Combinations (n-1, r,originalNumber, factorialResults)));   // this completes factorial for numerator
        
        factorialResults.put(n,result);   //result stored in the Map
        //System.out.println("getting result back out: " + factorialResults.get(n));
        
        
        if (n==originalNumber)   // this will occur once
        {
            
            denominator1 = originalNumber-r;        // originalNumber required since n has reduced as part of the recursive calls
           
            denominator2 = r;                       // r sample size has not changed
           
            
        // this is using the Java Memoization technique to ensure the factorial outcome is not calculated again, to save program cycles.
        // since the returns are done in reverse order.... n = 1 is processed first and n=6 last... Hence in practice
        // there will be entry in Map for all factorials, ready for the denominator..
        
        
            if (factorialResults.containsKey(denominator1) && factorialResults.containsKey(denominator2))
            
            {
                //System.out.println("here");
                //System.out.println("This is exact value of factorial  " + (denominator1) + " : " +  factorialResults.get(denominator1));
                //System.out.println("This is exact value of factorial  " + (denominator2) + " : " +  factorialResults.get(denominator2));
               
                return result / ((long) factorialResults.get(denominator1) * (long)factorialResults.get(denominator2));
            }
           
        }
           
            return result;
            
        }
          return 1;    // it should not reach here  
        }
        
    }